package com.carleasoft.mps.utils;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.carleasoft.mps.core.user.ThreadUserContext;
import com.carleasoft.mps.core.user.UserCacheData;
import com.carleasoft.mps.file.bean.entity.FileManage;
import com.carleasoft.mps.file.service.FileManageService;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class MinioUtils {
    @Autowired
    private MinioClient minioClient;

    @Value("${minio.bucketName}")
    private String bucketName;
    @Autowired
    private FileManageService fileManageService;



    public void makeDir(String dirName) {
        try {
            minioClient.putObject(    PutObjectArgs.builder().bucket(bucketName).object(dirName+"/")
                    .stream(new ByteArrayInputStream(new byte[] {}), 0, -1).build());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * description: 判断bucket是否存在，不存在则创建
     *
     * @return: void
     */
    public void existBucket(String name) {
        try {
            boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(name).build());
            if (!exists) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建存储bucket
     *
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
    public Boolean makeBucket(String bucketName) {
        try {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 删除存储bucket
     *
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
    public Boolean removeBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * description: 上传文件
     *
     * @param multipartFile
     * @return: java.lang.String
     */
    public List<String> upload(MultipartFile[] multipartFile) {
        List<String> names = new ArrayList<>(multipartFile.length);
        for (MultipartFile file : multipartFile) {
            String fileName = file.getOriginalFilename();
            String minioFileName=null;
            String[] split = fileName.split("\\.");
            if (split.length > 1) {
                fileName = split[0] + "_" + System.currentTimeMillis() + "." + split[1];
                minioFileName=split[0]+ "_" + System.currentTimeMillis() + "." + split[split.length-1];;
            } else {
                minioFileName=fileName;
                fileName = fileName + System.currentTimeMillis();
            }
            InputStream in = null;
            try {
                in = file.getInputStream();
                minioClient.putObject(PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .stream(in, in.available(), -1)
                        .contentType(file.getContentType())
                        .build()
                );
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            names.add(fileName);


            int index = file.getOriginalFilename().lastIndexOf(StrUtil.DOT);

            String fileSuffix = null;
            String filePath = null;
            if (index > 0) {
                fileSuffix = file.getOriginalFilename().substring(index);
                long size = file.getSize();
                filePath=this.getPreviewFileUrl(fileName);
                String idStr = IdWorker.getIdStr();
                this.createFileInfo(fileName, fileSuffix, filePath,minioFileName ,size,idStr);
                names.add(idStr);
            }
        }
        return names;
    }

    /**
     * 封装文件实体对象
     *
     * @param fileName
     * @param fileSuffix
     * @param filePath
     * @return
     */
    private void createFileInfo(String fileName, String fileSuffix, String filePath, String minioFileName,   long size , String id) {
        FileManage fileManage = new FileManage();
        fileManage.setId(id);
        fileManage.setName(fileName);
        fileManage.setFileName(fileName);
        fileManage.setFileSuffix(fileSuffix);
        fileManage.setFileSize(String.valueOf(size));
        fileManage.setMinioFileName(minioFileName);
        fileManage.setFilePath(filePath);
        // 获取当前登录人信息
        UserCacheData userInfo = ThreadUserContext.getCurrentCacheUser();
        if (null != userInfo) {
            fileManage.setCreatePersonCode(userInfo.getUserCode());
            fileManage.setCreatePersonName(userInfo.getUserName());
        }
        fileManageService.save(fileManage);
    }

    /**
     * 分片合并
     *
     * @param bucketName
     * @param folderName
     * @param partNum
     * @return
     */
    public Map<String,Object> uploadFileComplete(String bucketName, String folderName, String fileName, Integer partNum) {
        Map<String,Object> res = new HashMap<>();
        try {
            //获取临时文件下的所有文件信息
            Iterable<Result<Item>> listObjects = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(folderName + "/").build());

            //计算minio中分片个数
            Integer num = 0;
            for (Result<Item> result : listObjects) {
                num++;
            }
            //在依次校验实际分片数和预计分片数是否一致
            if (!num.equals(partNum)) {
                log.info("文件 {} 分片合并的时候，检测到实际分片数 {} 和预计分片数 {} 不一致", folderName, num, partNum);
                res.put("complete" ,false);
                return res;
            }

            InputStream inputStream = null;
            log.info("开始合并文件 {} 分片合并，实际分片数 {} 和预计分片数 {}", folderName, num, partNum);
            for (int i = 0; i < num; i++) {
                String tempName = folderName + "/" + fileName.substring(0, fileName.lastIndexOf(".")) + "_" + i + ".temp";
                try {
                    //获取分片文件流
                    InputStream response = minioClient.getObject(
                            GetObjectArgs.builder().bucket(bucketName).object(tempName).build());
                    //流合并
                    if (inputStream == null) {
                        inputStream = response;
                    } else {
                        inputStream = new SequenceInputStream(inputStream, response);
                    }
                } catch (Exception e) {
                    log.info("读取分片文件失败！", e);
                }
            }
            if (inputStream == null) {
                log.info("合并流数据为空！");
                res.put("complete" ,false);
                return res;
            }
            //转换为文件格式
            MockMultipartFile file = new MockMultipartFile(fileName, inputStream);

            String fileSuffix = null;
            String minioFileName = fileName.substring(0, fileName.lastIndexOf("."))
                    + "_" + new Date().getTime() + fileName.substring(fileName.lastIndexOf("."));
            res.put("fileName",minioFileName);
            fileSuffix = fileName.substring(fileName.lastIndexOf("."));
            long size = file.getSize();
            String idStr = IdWorker.getIdStr();
            this.createFileInfo(fileName, fileSuffix, "" ,minioFileName ,size,idStr);
            res.put("id",idStr);
            //将合并的文件流写入到minio中
            PutObjectArgs args = PutObjectArgs.builder()
                    .bucket(bucketName).object(minioFileName)
                    .stream(file.getInputStream(), file.getSize(), -1)
//                    .contentType(file.getContentType())//这里可以不知道类型
                    .build();
            String etag = minioClient.putObject(args).etag();

            // 删除临时文件
            if (etag != null) {
                listObjects.forEach(objectResult -> {
                    try {
                        Item item = objectResult.get();
                        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(item.objectName()).build());
                    } catch (Exception e) {
                        log.info("删除文件夹中的文件异常", e);
                    }
                });
                log.info("{}:临时文件夹文件已删除！", folderName);
            }

            inputStream.close();
            res.put("complete",true);
            return res;
        } catch (Exception e) {
            log.info("合并 {} - {} 文件失败！", folderName, fileName, e);
            res.put("complete" ,false);
            return res;
        }
    }

    /**
     * description: 下载文件
     *
     * @param fileName
     * @return: org.springframework.http.ResponseEntity<byte [ ]>
     */
    public ResponseEntity<byte[]> download(String fileName) {
        ResponseEntity<byte[]> responseEntity = null;
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
            out = new ByteArrayOutputStream();
            IOUtils.copy(in, out);
            //封装返回值
            byte[] bytes = out.toByteArray();
            HttpHeaders headers = new HttpHeaders();
            try {
                headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            headers.setContentLength(bytes.length);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setAccessControlExposeHeaders(Arrays.asList("*"));
            responseEntity = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseEntity;
    }

    /**
     * 查看文件对象
     *
     * @param bucketName 存储bucket名称
     * @return 存储bucket内文件对象信息
     */
    public List<ObjectItem> listObjects(String bucketName) {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).build());
        List<ObjectItem> objectItems = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                Item item = result.get();
                ObjectItem objectItem = new ObjectItem();
                objectItem.setObjectName(item.objectName());
                objectItem.setSize(item.size());
                objectItems.add(objectItem);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return objectItems;
    }

    /**
     * 批量删除文件对象
     */
    public void removeObjects(String objectName) {
        RemoveObjectArgs objectArgs = RemoveObjectArgs.builder().object(objectName)
                .bucket(bucketName).build();
        try {
            minioClient.removeObject(objectArgs);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String getPreviewFileUrl(String fileName){
        String res = "";
        try {
            res = minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .bucket(bucketName)
                            .object(fileName)
                            .method(Method.GET)
                            .expiry(7, TimeUnit.DAYS)
                            .build());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    public String getPresignedObjectUrl(String fileName) throws Exception {
        String url = minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket("test")
                        .object(fileName)
                        .build());
        return url;
    }

    /**
     * 文件上传文件
     *
     * @param file       文件
     * @param bucketName 桶名
     * @param objectName 文件名,如果有文件夹则格式为 "文件夹名/文件名"
     * @return
     */
    public Boolean uploadFile(MultipartFile file, String bucketName, String objectName) {
        if (Objects.isNull(file) || Objects.isNull(bucketName)) {
            throw new RuntimeException("文件或者桶名参数不全！");
        }
        try {
            //资源的媒体类型
            String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//默认未知二进制流
            InputStream inputStream = file.getInputStream();
            PutObjectArgs args = PutObjectArgs.builder()
                    .bucket(bucketName).object(objectName)
                    .stream(inputStream, file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build();
            ObjectWriteResponse response = minioClient.putObject(args);
            inputStream.close();
            return response.etag() != null;
        } catch (Exception e) {
            log.info("单文件上传失败！", e);
            return false;
        }
    }
}

